package com.xzzz.irda.guardcat.server.repos.memory;

import cn.hutool.core.collection.CollUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.xzzz.common.base.util.okhttp.HttpUtil;
import com.xzzz.common.base.util.json.JsonUtil;
import com.xzzz.irda.guardcat.core.constants.GuardcatConstant;
import com.xzzz.irda.guardcat.server.app.AppManager;
import com.xzzz.irda.guardcat.server.app.pojo.MachineInfo;
import com.xzzz.irda.guardcat.server.app.pojo.AppRelationEntity;
import com.xzzz.irda.guardcat.server.app.pojo.AppRelationVO;
import com.xzzz.irda.guardcat.server.metric.MetricRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;

/**
 * @author xzzz
 */
@Slf4j
@Service
public class AppRelationRepositoryInMemory extends MetricRepository<AppRelationEntity> {

    /**
     * 应用关系 Link, 后续缓存入 REDIS
     */
    private static final Set<AppRelationVO> APP_RELATION_LINKS = new HashSet<>();

    @Autowired
    private AppManager appManager;

    /**
     * 刷新应用关系
     *
     * @return 应用关系列表
     */
    public synchronized Set<AppRelationVO> refresh() {

        List<AppRelationEntity> relations = new ArrayList<>();

        for (String appName : appManager.getAppNames()) {
            List<MachineInfo> machines = appManager.getMachines(appName);
            if (CollUtil.isEmpty(machines)) {
                continue;
            }

            // 获取所有机器
            for (MachineInfo machine : machines) {
                // 非生产和在线的机器不查询应用关系
                if (machine.isNotProd() || !machine.isOnline()) {
                    continue;
                }

                AppRelationEntity relation = new AppRelationEntity();
                relation.setAppName(machine.getAppName());

                boolean hasRocketMq = getRelation(machine, relation);
                if (hasRocketMq) {
                    getMq(machine, relation);
                }
                relations.add(relation);
            }
        }
        this.buildRelation(relations);
        return APP_RELATION_LINKS;
    }

    /**
     * 查询RPC关系和 MQ 状态
     *
     * @param machine  机器
     * @param relation 关系
     * @return 该机器是否使用 MQ
     */
    private boolean getRelation(MachineInfo machine, AppRelationEntity relation) {
        try {
            String url = buildUrl(machine, GuardcatConstant.API.METRIC_APP_RELATION);
            String body = HttpUtil.get(url);
            JsonNode resp = JsonUtil.toJsonNode(body);

            // 获取RPC关系
            ArrayNode rpcRelation = (ArrayNode) resp.get("rpcRelation");
            if (CollUtil.isNotEmpty(rpcRelation)) {
                Set<String> rpcs = new HashSet<>();
                for (JsonNode node : rpcRelation) {
                    rpcs.add(node.asText());
                }
                relation.setRpcs(rpcs);
            }

            // 获取Feign关系
            ArrayNode feignRelation = (ArrayNode) resp.get("feignRelation");
            if (CollUtil.isNotEmpty(feignRelation)) {
                Set<String> feigns = new HashSet<>();
                for (JsonNode node : feignRelation) {
                    feigns.add(node.asText());
                }
                relation.setFeigns(feigns);
            }

            ArrayNode gateway = (ArrayNode) resp.get("gateway");
            if (CollUtil.isNotEmpty(gateway)) {
                Set<String> gateways = new HashSet<>();
                for (JsonNode node : gateway) {
                    gateways.add(node.asText());
                }
                relation.setGateways(gateways);
            }

            return resp.get("rocketmq").asBoolean();
        } catch (Exception e) {
            log.error("查询RPC调用关系失败: {}", machine.toLogString());
            return false;
        }
    }

    /**
     * 查询MQ关系
     *
     * @param machine  机器
     * @param relation 关系
     */
    private void getMq(MachineInfo machine, AppRelationEntity relation) {
        try {
            String url = buildUrl(machine, GuardcatConstant.API.METRIC_ROCKMET_TOPICS);
            String body = HttpUtil.get(url);
            JsonNode resp = JsonUtil.toJsonNode(body);
            ArrayNode producer = (ArrayNode) resp.get("producer");
            ArrayNode consumer = (ArrayNode) resp.get("consumer");

            if (CollUtil.isNotEmpty(producer)) {
                Set<String> topic = new HashSet<>();
                for (JsonNode node : producer) {
                    topic.add(node.asText());
                }
                relation.setProducerMqTopic(topic);
            }

            if (CollUtil.isNotEmpty(consumer)) {
                Set<String> topic = new HashSet<>();
                for (JsonNode node : consumer) {
                    topic.add(node.asText());
                }
                relation.setConsumerMqTopic(topic);
            }
        } catch (Exception e) {
            log.error("查询MQ调用关系失败: {}", machine.toLogString());
        }
    }

    /**
     * 将应用的调用关系构造成 echart link 集合
     *
     * @param relations 应用的调用关系
     */
    private void buildRelation(List<AppRelationEntity> relations) {
        APP_RELATION_LINKS.clear();

        // key: 根据应用排序后的 source 和 target
        final Map<String, Integer> relationNum = new HashMap<>(50);
        final Map<String, Map<String, Set<String>>> relationTopic = new HashMap<>(50);

        for (AppRelationEntity relation : relations) {

            // 构造PRC的之间的link
            if (CollUtil.isNotEmpty(relation.getRpcs())) {
                for (String rpcTarget : relation.getRpcs()) {
                    AppRelationVO vo = new AppRelationVO();
                    vo.setSource(relation.getAppName().toLowerCase());
                    vo.setTarget(rpcTarget.toLowerCase());
                    vo.setType("rpc");
                    Integer num = relationNum.get(vo.getSortName());
                    if (num == null) {
                        relationNum.put(vo.getSortName(), 1);
                        vo.setNum(1);
                    } else {
                        relationNum.put(vo.getSortName(), ++num);
                        vo.setNum(num);
                    }
                    APP_RELATION_LINKS.add(vo);
                }
            }

            // 构造PRC的之间的link
            if (CollUtil.isNotEmpty(relation.getFeigns())) {
                for (String feignTarget : relation.getFeigns()) {
                    AppRelationVO vo = new AppRelationVO();
                    vo.setSource(relation.getAppName().toLowerCase());
                    vo.setTarget(feignTarget.toLowerCase());
                    vo.setType("feign");
                    Integer num = relationNum.get(vo.getSortName());
                    if (num == null) {
                        relationNum.put(vo.getSortName(), 1);
                        vo.setNum(1);
                    } else {
                        relationNum.put(vo.getSortName(), ++num);
                        vo.setNum(num);
                    }
                    APP_RELATION_LINKS.add(vo);
                }
            }

            // 构造GATEWAY的之间的link
            if (CollUtil.isNotEmpty(relation.getGateways())) {
                for (String gatewayTarget : relation.getGateways()) {
                    AppRelationVO vo = new AppRelationVO();
                    vo.setSource(relation.getAppName().toLowerCase());
                    vo.setTarget(gatewayTarget.toLowerCase());
                    vo.setType("gateway");
                    Integer num = relationNum.get(vo.getSortName());
                    if (num == null) {
                        relationNum.put(vo.getSortName(), 1);
                        vo.setNum(1);
                    } else {
                        relationNum.put(vo.getSortName(), ++num);
                        vo.setNum(num);
                    }
                    APP_RELATION_LINKS.add(vo);
                }
            }

            if (CollUtil.isNotEmpty(relation.getProducerMqTopic())) {
                for (String pTopic : relation.getProducerMqTopic()) {
                    Map<String, Set<String>> producerKey = relationTopic.computeIfAbsent(pTopic, k -> new HashMap<>());
                    Set<String> producers = producerKey.computeIfAbsent("producer", k -> new HashSet<>());
                    producers.add(relation.getAppName());
                }
            }

            if (CollUtil.isNotEmpty(relation.getConsumerMqTopic())) {
                for (String cTopic : relation.getConsumerMqTopic()) {
                    Map<String, Set<String>> consumerKey = relationTopic.computeIfAbsent(cTopic, k -> new HashMap<>());
                    Set<String> consumers = consumerKey.computeIfAbsent("consumer", k -> new HashSet<>());
                    consumers.add(relation.getAppName());
                }
            }
        }

        for (Map.Entry<String, Map<String, Set<String>>> stringMapEntry : relationTopic.entrySet()) {
            final String topic = stringMapEntry.getKey();
            if (CollUtil.isEmpty(stringMapEntry.getValue().get("producer")) ||
                    CollUtil.isEmpty(stringMapEntry.getValue().get("consumer"))) {
                continue;
            }

            for (String producer : stringMapEntry.getValue().get("producer")) {
                for (String consumer : stringMapEntry.getValue().get("consumer")) {
                    AppRelationVO vo = new AppRelationVO();
                    vo.setSource(producer.toLowerCase());
                    vo.setTarget(consumer.toLowerCase());
                    vo.setValue(topic);
                    vo.setType("rocketmq");
                    Integer num = relationNum.get(vo.getSortName());
                    if (num == null) {
                        relationNum.put(vo.getSortName(), 1);
                        vo.setNum(1);
                    } else {
                        relationNum.put(vo.getSortName(), ++num);
                        vo.setNum(num);
                    }
                    APP_RELATION_LINKS.add(vo);
                }
            }

        }

//        for (AppRelationVO vo : APP_RELATION_LINKS) {
//            System.out.println(vo.toString());
//        }
    }

    @Override
    public void save(MachineInfo machineInfo, AppRelationEntity appRelationEntity) {

    }

    @Override
    public void expire() {

    }

    @Override
    public AppRelationEntity getByMachineId(String machineId) {
        return null;
    }

    @Override
    public List<AppRelationEntity> listByMachineId(String machineId) {
        return null;
    }
}
